Условная компиляция
===================

.. index::
    условная компиляция
    debug
    version
    static if

Условная компиляция предназначена для компиляции частей программ специальным образом
в зависимости от определенных условий времени компиляции. Иногда целые разделы
программы могут быть изъяты и не компилироваться вообще.
Условная компиляция включает проверки условий, которые оцениваются во
время компиляции. Условные операторы времени выполнения, такие как if, for, while, не являются
не являются функциями условной компиляции.
В предыдущих главах мы уже сталкивались с некоторыми функциями, которые
которые можно рассматривать как условную компиляцию:
блоки ∙unittest компилируются и выполняются только в том случае, если включен ключ компилятора -unittest
включен.
∙ Блоки контрактного программирования in, out и invariant активизируются только в том случае.
если не включен переключатель компилятора -release.
Юнит-тесты и контракты связаны с корректностью программы;
Включены ли они в программу или нет, не должно менять поведение программы.
Ниже перечислены особенности D, которые специально предназначены для условной
компиляции:

* debug
* version
* static if
*  is выражение
* __traits

С выражением is мы познакомимся в следующей главе

debug
------

Отладка полезна во время разработки программы. Выражения и утверждения
помеченные как отладочные, компилируются в программу только в том случае, если включен ключ -debug компилятора:

.. code-block:: d

    debug a_conditionally_compiled_expression;

    debug {
    // ... conditionally compiled code ...
    } else {
    // ... code that is compiled otherwise ...
    }

Предложение else является необязательным.
И одиночное выражение, и приведенный выше блок кода компилируются только в том случае.
включен ключ компилятора -debug.
Мы добавляли в программы операторы, которые печатали на выходе сообщениятипа "сложение", "вычитание" и т. д. на выходе. Такие сообщения (они же логи и лог
сообщения) помогают найти ошибки, визуализируя шаги, выполняемые
программа.
Вспомните функцию binarySearch() из главы "Шаблоны" (:ref:`Шаблоны<глава_64_start>`). Следующая версия функции намеренно некорректна:

.. code-block:: d

    import std.stdio;

    // WARNING! This algorithm is wrong
    size_t binarySearch(const int[] values, int value)
    {
        if (values.length == 0)
        {
            return size_t.max;
        }
        immutable midPoint = values.length / 2;
        if (value == values[midPoint])
        {
            return midPoint;
        }
        else if (value < values[midPoint])
        {
            return binarySearch(values[0 .. midPoint], value);
        }
        else
        {
            return binarySearch(values[midPoint + 1 .. $], value);
        }
    }

    void main()
    {
        auto numbers = [-100, 0, 1, 2, 7, 10, 42, 365, 1000];
        auto index = binarySearch(numbers, 42);
        writeln("Index: ", index);
    }

Хотя индекс 42 равен 6, программа ошибочно сообщает 1:

.. code-block:: console

    Index: 1

Один из способов найти ошибку в программе - вставить строки, которые выводят сообщения:

.. code-block:: d

    size_t binarySearch(const int[] values, int value)
    {
        writefln("searching %s among %s", value, values);
        if (values.length == 0)
        {
            writefln("%s not found", value);
            return size_t.max;
        }
        immutable midPoint = values.length / 2;
        writefln("considering index %s", midPoint);
        if (value == values[midPoint])
        {
            writefln("found %s at index %s", value, midPoint);
            return midPoint;
        }
        else if (value < values[midPoint])
        {
            writefln("must be in the first half");
            return binarySearch(values[0 .. midPoint], value);
        }
        else
        {
            writefln("must be in the second half");
            return binarySearch(values[midPoint + 1 .. $], value);
        }
    }

Теперь вывод программы включает шаги, которые она выполняет:

.. code-block:: console

    searching 42 among [-100, 0, 1, 2, 7, 10, 42, 365, 1000]
    considering index 4
    must be in the second half
    searching 42 among [10, 42, 365, 1000]
    considering index 2
    must be in the first half
    searching 42 among [10, 42]
    considering index 1
    found 42 at index 1
    Index: 1

Предположим, что предыдущий вывод действительно поможет программисту найти ошибку
ошибку. Очевидно, что выражения writefln() больше не нужны после того, как
когда ошибка найдена и исправлена. Однако удаление этих строк также может быть
поскольку они могут пригодиться в будущем.
Вместо того чтобы удалять эти строки совсем, их можно пометить как отладочные:

.. code-block:: d

    debug writefln("%s not found", value);

Такие строки включаются в программу только в том случае, если включен ключ компилятора -debug
включен:

.. code-block:: console

    $ dmd deneme.d -ofdeneme -w -debug

**debug(tag)**

Если в программе много ключевых слов отладки, возможно, в несвязанных частях, то
вывод может стать слишком перегруженным. Чтобы избежать этого, отладочным словам можно
присвоить имена (теги), чтобы включать их в программу выборочно:

.. code-block:: d

    debug(binarySearch) writefln("%s not found", value);

Отладочные операторы с метками включаются с помощью ключа компилятора -debug=tag:

.. code-block:: console

    $ dmd deneme.d -ofdeneme -w -debug=binarySearch

Отладочные блоки также могут иметь теги:

.. code-block:: d

    debug(binarySearch) {
    // ...
    }

Можно включить несколько отладочных тегов одновременно:

.. code-block:: console

    $ dmd deneme.d -w -debug=binarySearch -debug=stackContainer

В этом случае будут включены оба отладочных оператора, а также binarySearch и stackContainer
будут включены.

**debug(level)**

Иногда полезнее связывать отладочные операторы по числовым уровням.
Повышение уровня позволяет получить более подробную информацию:

.. code-block:: d

    debug import std.stdio;

    void myFunction(string fileName, int[] values)
    {
        debug (1)
            writeln("введена функция myFunction");
        debug (2)
        {
            writeln("аргументы:");
            writeln(" имя файла: ", fileName);
            foreach (i, value; values)
            {
                writefln(" %4s: %s", i, value);
            }
        }
        // ... реализация функции ...
    }

    void main()
    {
        myFunction("deneme.txt", [10, 4, 100]);
    }

Отладочные выражения и блоки, которые ниже или равны указанному
уровня, будут скомпилированы:

.. code-block:: d

    $ dmd deneme.d -w -debug=1
    $ ./deneme
    entered myFunction

Следующий вариант даст больше информации:

.. code-block:: console

    $ dmd deneme.d -w -debug=2
    $ ./deneme
    entered myFunction
    the arguments:
    file name: deneme.txt
    0: 10
    1: 4
    2: 100

version(tag) и version(level)
-----------------------------

version аналогична отладке и используется так же:

.. code-block:: d

    version (testRelease) /* ... an expression ... */ ;
    version (schoolRelease)
    {
        /* ... expressions that are related to the version of
        *     this program that is presumably shipped to schools ... */
    }
    else
    {
        // ... code compiled otherwise ...
    }
    version (1) aVariable = 5;
    version (2)
    {
        // ... a feature of version 2 ...
    }

Предложение else является необязательным.
Хотя version работает по сути так же, как и debug, наличие отдельных
ключевых слов помогает различать их несвязанные применения.
Как и в случае с debug, можно включить более одной версии:

.. code-block:: console

    dmd deneme.d -w -version=record -version=precise_calculation

Существует множество предопределенных тегов версий, полный список которых можно найти в
спецификации условной компиляции1. Приведенный ниже краткий список является лишь выборкой:

.. list-table:: Права доступа
   :widths: 25 50
   :header-rows: 1
   :align: center

   * - Тег
     - Описание
   * - The compiler
     - DigitalMars GNU LDC SDC
   * - The operating system
     - Windows Win32 Win64 linux OSX Posix FreeBSD OpenBSD NetBSD DragonFlyBSD BSD Solaris AIX Haiku SkyOS SysV3 SysV4 Hurd
   * - CPU endianness
     - LittleEndian BigEndian
   * - Enabled compiler switches
     - D_Coverage D_Ddoc D_InlineAsm_X86 D_InlineAsm_X86_64 D_LP64 D_PIC D_X32 D_HardFloat D_SoftFloat D_SIMD D_Version2 D_NoBoundsChecks unittest assert
   * - CPU architecture
     - X86 X86_64
   * - Platform
     - Android Cygwin MinGW ARM ARM_Thumb ARM_Soft ARM_SoftFP ARM_HardFP ARM64 PPC PPC_SoftFP PPC_HardFP PPC64 IA64 MIPS MIPS32 MIPS64 MIPS_O32 MIPS_N32 MIPS_O64 MIPS_N64 MIPS_EABI MIPS_NoFloat MIPS_SoftFloat MIPS_HardFloat SPARC SPARC_V8Plus SPARC_SoftFP SPARC_HardFP SPARC64 S390 S390X HPPA HPPA64 SH SH64 Alpha Alpha_SoftFP Alpha_HardFP
   * - etc.
     - etc.

Кроме того, существуют следующие два специальных тега версии:
∙none: Этот тег никогда не определяется; он полезен для отключения блоков кода.
∙all: Этот тег определяется всегда; он полезен для включения блоков кода.
В качестве примера использования предопределенных тегов версий ниже приведен
отрывок (здесь он отформатирован по-другому) из модуля std.ascii, который предназначен для
определения последовательности символов новой строки для системы (статическое утверждение будет
будет объяснено ниже):

.. code-block:: d

    version (Windows)
    {
        immutable newline = "\r\n";
    }
    else version (Posix)
    {
        immutable newline = "\n";
    }
    else
    {
        static assert(0, "Unsupported OS");
    }

Присвоение идентификаторов debug и version
-------------------------------------------

Как и переменным, debug и version можно присвоить идентификаторы. В отличие от
переменных, такое присвоение не изменяет никакого значения, а активирует указанный
идентификатор.

.. code-block:: d

    import std.stdio;

    debug (everything)
    {
        debug = binarySearch;
        debug = stackContainer;
        version = testRelease;
        version = schoolRelease;
    }
    void main()
    {
        debug (binarySearch)
            writeln("binarySearch is active");
        debug (stackContainer)
            writeln("stackContainer is active");
        version (testRelease)
            writeln("testRelease is active");
        version (schoolRelease)
            writeln("schoolRelease is active");
    }

Назначения внутри приведенного выше блока debug(everything) активируют все
указанные идентификаторы:

.. code-block:: console

   $ dmd deneme.d -w -debug=everything

   $ ./deneme
   binarySearch активен
   stackContainer активен
   testRelease активен
   schoolRelease активен

static if
---------

static if - это эквивалент оператора if во время компиляции.
Как и оператор if, static if принимает логическое выражение и оценивает его
его. В отличие от оператора if, статическое if не связано с потоком выполнения; скорее, оно
он определяет, следует ли включать тот или иной фрагмент кода в программу или нет.
Логическое выражение должно быть оцениваемым во время компиляции. Если логическое
выражение равно true, то код внутри статического if будет скомпилирован. Если
условие ложно, код не включается в программу, как будто он никогда не был
как будто он никогда не был написан. В логических выражениях обычно используется выражение is
и __traits. static if может появляться в области видимости модуля или внутри определений struct, class,
шаблона и т. д. В качестве опции могут присутствовать предложения else.
Давайте применим static if в простом шаблоне, используя выражение is.
Другие примеры использования static if мы рассмотрим в следующей главе:

.. code-block:: d

    import std.stdio;

    struct MyType(T)
    {
        static if (is(T == float))
        {
            alias ResultType = double;
        }
        else static if (is(T == double))
        {
            alias ResultType = real;
        }
        else
        {
            static assert(false, T.stringof ~ " is not supported");
        }
        ResultType doWork()
        {
            writefln("The return type for %s is %s.",
                T.stringof, ResultType.stringof);
            ResultType result;
            // ...
            return result;
        }
    }

    void main()
    {
        auto f = MyType!float();
        f.doWork();
        auto d = MyType!double();
        d.doWork();
    }


Согласно коду, MyType может использоваться только с двумя типами: float или double.
Возвращаемый тип doWork() выбирается в зависимости от того, для какого типа шаблон
инстанцирован для float или double:

.. code-block:: console

    The return type for float is double.
    The return type for double is real.

Обратите внимание, что при цепочке статических выражений if необходимо писать else static if.
В противном случае написание else if приведет к вставке этого условия if в
которая, естественно, будет выполнена во время выполнения.

static assert
--------------

Хотя это и не является функцией условной компиляции, я решил ввести
static assert.
static assert - это эквивалент assert во время компиляции. Если условное
выражение ложно, компиляция будет прервана из-за неудачи утверждения.
Как и static if, static assert может появляться в любой области видимости в программе.
Пример статического assert мы видели в программе выше: Там,
компиляция прерывается, если T - любой тип, отличный от float или double:

.. code-block:: d

    auto i = MyType!int();

Компиляция прерывается с сообщением, которое было передано static assert:

.. code-block:: console

    Error: static assert  "int is not supported".

В качестве другого примера предположим, что определенный алгоритм может работать только с
типами, кратными определенному размеру. Такое условие может быть обеспечено на
время компиляции с помощью статического утверждения:

.. code-block:: d

    T myAlgorithm(T)(T value)
    {
        /* This algorithm requires that the size of type T is a
    * multiple of 4. */
        static assert((T.sizeof % 4) == 0);
        // ...
    }

Если бы функция была вызвана с символом char, компиляция была бы прервана со следующим сообщением об ошибке
следующим сообщением об ошибке:

.. code-block:: console

    Error: static assert  (1LU == 0LU) is false

Такая проверка предотвращает работу функции с несовместимым типом,
что может привести к неправильным результатам.
Статический assert можно использовать с любым логическим выражением, которое можно оценить во время
время компиляции.


Type traits
------------

Ключевое слово __traits и модуль std.traits предоставляют информацию о
типах и выражениях во время компиляции.
Некоторая информация, собираемая компилятором, становится доступной программе
программе с помощью __traits. Его синтаксис включает ключевое слово traits и параметры, которые
относящиеся к этому ключевому слову:

.. code-block:: d

    __traits(keyword, parameters)

Ключевые слова определяют информацию, которая запрашивается. Параметры представляют собой либо
типы или выражения, значения которых определяются каждым конкретным
ключевым словом.
Информация, которую можно получить с помощью __traits, особенно полезна в
шаблонах. Например, ключевое слово isArithmetic может определить, является ли
конкретный параметр шаблона T является арифметическим типом:

.. code-block:: d

    static if (__traits(isArithmetic, T))
    {
        // ... an arithmetic type ...
    }
    else
    {
        // ... not an arithmetic type ...
    }

Аналогично, модуль std.traits предоставляет информацию во время компиляции через
свои шаблоны. Например, std.traits.isSomeChar возвращает true, если параметр шаблона
является символьным типом:

.. code-block:: d

    import std.traits;

    // ...
    static if (isSomeChar!T)
    {
        // ... char, wchar, or dchar ...
    }
    else
    {
        // ... not a character type ...
    }

Please refer to the __traits documentation (???) and the std.traits
documentation (???) for more information.

Резюме
------
* Код, определенный как отладочный, включается в программу только в том случае, если используется ключ компилятора -debug
  используется ключ компилятора.
* Код, определяемый как version, включается в программу только в том случае, если используется соответствующий ключ компилятора
  если используется соответствующий ключ компилятора -version.
* static if похож на оператор if, который выполняется во время компиляции. Он
  вводит код в программу в зависимости от определенных условий компиляции
  условий.
* static assert проверяет предположения о коде во время компиляции.
* traits и std.traits предоставляют информацию о типах во время компиляции.
